package net.aiion.weave.spi.bytecode.util;

import java.util.ArrayList;
import java.util.Collection;

import net.aiion.weave.spi.bytecode.BytecodeContext;
import net.aiion.weave.spi.bytecode.BytecodeSink;
import net.aiion.weave.spi.bytecode.events.BytecodeEvent;

public abstract class BytecodeTransformation implements BytecodeSink
{
    private final static int ACTION_IGNORE = 1; // Ignore all event's for the current sink, until no other action is
                                                // enabled
    private final static int ACTION_RETAIN = 2; // Retain all event's for the current sink, until no other action is
                                                // enabled
    private final static int ACTION_PROCESS = 3; // Process all event's for the current sink, until no other action is
                                                 // enabled
    private final static int ACTION_FORWARD = 4; // Forward all event's for the current sink, until no other action is
                                                 // enabled
    private final static int ACTION_SKIP = 5; // Skip all event's for the current sink

    private final Collection<BytecodeEvent> retainedBytecodeEvents;

    private int currentAction = ACTION_PROCESS;

    public BytecodeTransformation() {
        retainedBytecodeEvents = new ArrayList<>();
    }

    @Override
    public final void handleBytecodeEvent(final BytecodeContext aBytecodeContext) {
        final BytecodeEvent aBytecodeEvent = aBytecodeContext.getBytecodeEvent();

        currentAction = decideAction(aBytecodeContext, aBytecodeEvent);

        switch (currentAction) {
        case ACTION_IGNORE:
            break;
        case ACTION_RETAIN:
            retainBytecodeEvent(aBytecodeContext, aBytecodeEvent);
            break;
        case ACTION_PROCESS:
            processBytecodeEvent(aBytecodeContext, aBytecodeEvent);
            break;
        case ACTION_FORWARD:
            forwardBytecodeEvent(aBytecodeContext, aBytecodeEvent);
            break;
        case ACTION_SKIP:
            forwardBytecodeEvent(aBytecodeContext, aBytecodeEvent);
            break;
        }
    }

    private int decideAction(final BytecodeContext aBytecodeContext, final BytecodeEvent aBytecodeEvent) {
        if (currentAction == ACTION_SKIP) {
            return ACTION_SKIP;
        }

        if (currentAction != ACTION_IGNORE && testIgnore(aBytecodeContext, aBytecodeEvent)) {
            return ACTION_IGNORE;
        }

        if (currentAction != ACTION_RETAIN && testRetain(aBytecodeContext, aBytecodeEvent)) {
            return ACTION_RETAIN;
        }

        if (currentAction != ACTION_PROCESS && testProcess(aBytecodeContext, aBytecodeEvent)) {
            return ACTION_PROCESS;
        }

        if (currentAction != ACTION_FORWARD && testForward(aBytecodeContext, aBytecodeEvent)) {
            return ACTION_FORWARD;
        }

        if (currentAction != ACTION_SKIP && testSkip(aBytecodeContext, aBytecodeEvent)) {
            return ACTION_SKIP;
        }

        return currentAction;
    }

    private void retainBytecodeEvent(final BytecodeContext aBytecodeContext, final BytecodeEvent aBytecodeEvent) {
        retainedBytecodeEvents.add(aBytecodeEvent);

        if (aBytecodeEvent.getType() == BytecodeEvent.CLASS_END) {
            for (BytecodeEvent bytecodeEvent1 : retainedBytecodeEvents) {
                aBytecodeContext.forwardBytecodeEvent(bytecodeEvent1);
            }

            retainedBytecodeEvents.clear();
        }
    }

    private void processBytecodeEvent(final BytecodeContext aBytecodeContext, final BytecodeEvent aBytecodeEvent) {
        for (BytecodeEvent bytecodeEvent : retainedBytecodeEvents) {
            handleBytecodeEvent(aBytecodeContext, bytecodeEvent);
        }

        retainedBytecodeEvents.clear();
        handleBytecodeEvent(aBytecodeContext, aBytecodeEvent);
    }

    private void forwardBytecodeEvent(final BytecodeContext aBytecodeContext, final BytecodeEvent aBytecodeEvent) {
        aBytecodeContext.forwardBytecodeEvent(aBytecodeEvent);
    }

    public boolean testIgnore(BytecodeContext aBytecodeContext, BytecodeEvent aBytecodeEvent) {
        return false;
    }

    public boolean testRetain(BytecodeContext aBytecodeContext, BytecodeEvent aBytecodeEvent) {
        return false;
    }

    public boolean testProcess(BytecodeContext aBytecodeContext, BytecodeEvent aBytecodeEvent) {
        return false;
    }

    public boolean testForward(BytecodeContext aBytecodeContext, BytecodeEvent aBytecodeEvent) {
        return false;
    }

    public boolean testSkip(BytecodeContext aBytecodeContext, BytecodeEvent aBytecodeEvent) {
        return false;
    }

    public abstract void
        handleBytecodeEvent(final BytecodeContext aBytecodeContext, final BytecodeEvent aBytecodeEvent);
}
